/**********************************************
* SFW_TcpPort.cpp
***********************************************/
#include "framework.h"
#include "SysConfigInfo.h"

uint16_t SFW_TcpPort::globeUdpPort = 50000;
string SFW_TcpPort::selfIpv4 = "192.168.1.125";

/**********************************************
* constructor
**********************************************/
SFW_TcpPort::SFW_TcpPort()
{
	connfd = -1; 
	rmotfd = -1;
	initialize();
}

SFW_TcpPort::SFW_TcpPort(uint16_t varPort)
:port(varPort)
{
	initialize();
}

SFW_TcpPort::~SFW_TcpPort()
{
	
}

/**********************************************
* member method
* initialize 
**********************************************/
void SFW_TcpPort::initialize()
{
	currType = Other_Type;
	tcpStatus = stInit;
	udpClientInfo = NULL;
	memset(&cliInfo, 0x00, sizeof(cliInfo));
	tcpStatus = stClientConnect;
}

/**********************************************
* member method
* fdset 
**********************************************/
void SFW_TcpPort::fdset(fd_set* pRset, int* pMaxfd)
{
	int maxfd = *pMaxfd;
	switch(tcpStatus)
	{
		case stInit:
		case stClientConnect:
		case stClientClosed:
		case stRrmoteClosed:
			break;
		
		case stRquest:
		case stProcedure:
			FD_SET(connfd, pRset);
			maxfd = (maxfd > connfd ? maxfd : connfd);
			break;
		
		case stEstablish:
			/* 2013/07/20 12:38 */
			FD_SET(connfd, pRset);
			maxfd = (maxfd > connfd ? maxfd : connfd);
			FD_SET(rmotfd, pRset);
			maxfd = (maxfd > rmotfd ? maxfd : rmotfd);
			break;
			
		default:
			break;
	}
	*pMaxfd = maxfd;
}

/**********************************************
* member method
* ClientConnect 
**********************************************/
int SFW_TcpPort::ClientConnect(int sockfd)
{	
	socklen_t recvlen = sizeof(struct sockaddr);

	switch(tcpStatus)
	{
	//case stEstablish:
	case stClientConnect:
	case stClosed:
		connfd = accept(sockfd, (SA*)&cliInfo, &recvlen);
		if(connfd < 0)
		{
			LOG_FATAL("accept failure(@@@)!");
			return -1;
		}
		connectionInfo.setIPAndPort(&cliInfo, connectionInfo.client);
		break;
		
	default:
		return 1;
	}

	tcpStatus = stProcedure;
	return 0;
}

/**********************************************
* member method
* RecvFromClient 
**********************************************/
void SFW_TcpPort::RecvFromClient()
{
	int rtn = 0;
	SFW_Stream bufRes(10);
	SFW_Stream bufMRes(2);

	bufMRes.initialize();
	bufRes.initialize();

	char bufRcv[102400];
	memset(bufRcv, 0x00, sizeof(bufRcv));
	SFW_String msglog;
	
	switch(tcpStatus)
	{
	case stProcedure:
		rtn = read(connfd, bufRcv, sizeof(bufRcv));
		if(rtn == 0)
		{
			LOG_FATAL("client close(@@@)!");
			close(connfd);
			tcpStatus = stClosed;
			return;
		}
		
		if(checkMethodAndResponse(rtn, bufRcv, bufMRes) != 0)
		{
			/* check failure... */
			//return;
		}
		
		write(connfd, bufMRes.getBuffer(), bufMRes.getLength());

		tcpStatus = stRquest;
		break;
	
	case stRquest:
		/* deserialize request */
		rtn = read(connfd, bufRcv, sizeof(bufRcv));
		if(rtn == 0)
		{
			LOG_FATAL("client close(@@@)!");
			close(connfd);
			tcpStatus = stClosed;
			return;
		}
		
		if(checkRequestAndRespose(rtn, bufRcv, bufRes) != 0)
		{
			/* check request failure... */
			LOG_FATAL("check request failure(@@@)!");
			
			close(connfd);
			tcpStatus = stClosed;
			return;
		}
		
		write(connfd, bufRes.getBuffer(), bufRes.getLength());
		tcpStatus = stEstablish;
		/* Conntion Infomation */
		connectionInfo.DisplayInfo("OPEN");
		break;
		
	case stEstablish:
		break;
	
	case stRrmoteClosed:
		if(rtn == 0)
		{
			/* Conntion Infomation */
			connectionInfo.DisplayInfo("CLOSE");
			tcpStatus = stClosed;
			return;
		}
	
	default :
		return ;
	}
}

/**********************************************
* member method
* RecvFromRemote 
**********************************************/
void SFW_TcpPort::RecvFromRemote()
{
	int rtn = 0;
	Socks_UDPData udpdata;
	SFW_Stream* udpstream;
	struct sockaddr_in tmpSockInfo;
	SFW_String msglog;
	char bufRcv[102400];
	
	socklen_t sndlen = sizeof(struct sockaddr);
	socklen_t socklen = sizeof(struct sockaddr);
	rtn = recvfrom(rmotfd, bufRcv, sizeof(bufRcv), 0, (SA*)&tmpSockInfo, &socklen);
	
	switch(tcpStatus)
	{
		case stEstablish:
			if(currType == UDP_Type)
			{
				if(bufRcv[0] == 0x00 && bufRcv[1] == 0x00)
				{
					if(NULL == udpClientInfo)
					{
						udpClientInfo = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
						memcpy((char*)udpClientInfo, (char*)&tmpSockInfo, sizeof(struct sockaddr_in));
					}
					
					udpstream = new SFW_Stream(rtn + 1);
					udpstream->initialize();
					memcpy(udpstream->buffer, bufRcv, rtn);
					udpdata.deserialize(*udpstream);
				
					struct sockaddr_in sndInfo;
					sndInfo.sin_family = AF_INET;
					sndInfo.sin_port = htons(udpdata.dstInfo.dest_port);
					sndInfo.sin_addr.s_addr = *(uint32_t*)getAddressNum(udpdata.atyp, udpdata.dstInfo.dest_addr);
					
					sendto(rmotfd, udpdata.data, udpdata.datalen, 0, (SA*)&sndInfo, sndlen);
					
					msglog.sprintf("[UDP](C) ==> (R). receive length %d", udpdata.datalen);
				}
				
				else
				{
					if(NULL == udpClientInfo)
						break;
					udpdata.rsv[0] = 0x00;
					udpdata.rsv[1] = 0x00;
					udpdata.frag = 0x00;
					udpdata.atyp = 0x01;
					udpdata.dstInfo.dest_addr = (char*)malloc(4);
					memcpy(udpdata.dstInfo.dest_addr, (char*)inet_addr(selfIpv4.c_str()), 4);
					udpdata.dstInfo.dest_port = htons(udpport);
					udpdata.datalen = (unsigned short)rtn; 
					udpdata.data = bufRcv;
					
					udpdata.serialize(*udpstream);
					
					sendto(rmotfd, udpstream->getBuffer(), udpstream->getLength(), 0, (SA*)udpClientInfo, sndlen);
					msglog.sprintf("[UDP](R) ==> (C). receive length %d", udpdata.datalen);
				}
			}
		case stClientClosed:
			if(rtn == 0)
			{
				//::close(rmotfd);
				::shutdown(rmotfd, SHUT_RD);
				sleep(2);
				
				/* Conntion Infomation */
				connectionInfo.DisplayInfo("CLOSE");
				tcpStatus = stClosed;
				return;
			}
			
		default:
			return;
	}
}

/**********************************************
* member method
* getAddressNum 
**********************************************/
char* SFW_TcpPort::getAddressNum(char atyp, char* buf)
{
	if(atyp == 0x01)
	{
		/* IPv4 do nothing */
		return buf;
	}

	else if(atyp == 0x03)
	{
		string strName;
		char **pptr;
		char str[INET_ADDRSTRLEN];
		struct hostent *hptr;

		strName = buf;
		
		hptr = gethostbyname(strName.c_str());
		if(NULL == hptr)
		{
			LOG_FATAL("gethostbyname failure(%s).", hstrerror(h_errno));
			return NULL;
		}

		for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
		{
			string msglog = *pptr;
			LOG_INFO("alias :%s", msglog.c_str());
		}

		switch(hptr->h_addrtype)
		{
		case AF_INET:
			pptr = hptr->h_addr_list;
			for(; *pptr != NULL; *pptr++)
			{
				LOG_INFO("address :%s", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
			}
		}
		return hptr->h_addr_list[0];
	}

	else
	{
		/* IPv6 */

		return NULL;
	}
}

/**********************************************
* member method
* checkMethodAndResponse 
**********************************************/
int SFW_TcpPort::checkMethodAndResponse(int recvLen, char* recvBuf, SFW_Stream& sendBuf)
{
	SOCKS_MethodReplies mResponse;
	SFW_Stream bufMReq;
	SOCKS_MethodRequest mRequest;

	/* deserialize request */
	bufMReq.length = recvLen;
	bufMReq.initialize();
	
	memcpy(bufMReq.buffer, recvBuf, bufMReq.length);
	
	mRequest.deserialize(bufMReq);
	/* check method request */
	if(SystemConfigInfo::getVersion() != (int)mRequest.ver)
	{
		LOG_FATAL("different version. request(%d); real(%d).", (int)mRequest.ver, SystemConfigInfo::getVersion());
		return -1;
	}
	char selectMethod = SystemConfigInfo::getSelectedMethod(mRequest.method_num, mRequest.method);
	
	/* make replies */
	LOG_INFO("ver(%d),method(%d)", (int)mRequest.ver, (int)selectMethod);
	
	mResponse.ver = mRequest.ver;
	mResponse.method= selectMethod;
	mResponse.serialize(sendBuf);

	return 0;
}

/**********************************************
* member method
* checkRequestAndRespose 
**********************************************/
int SFW_TcpPort::checkRequestAndRespose(int recvLen, char* recvbuf, SFW_Stream& sendBuf)
{
	SFW_Stream bufReq;
	SOCKS_Request req;
	SOCKS_Replies* res;

	char repValue = 0x00; // result OK
	int varErrno;
	int result;
	char* Ipbuf = NULL; // initialize NULL
	struct sockaddr_in rmotSvrInfo;
	socklen_t rmotSvrLen = sizeof(struct sockaddr);
	memset(&rmotSvrInfo, 0x00, sizeof(struct sockaddr_in));
	res = new SOCKS_Replies();
	bufReq.length = recvLen;
	bufReq.initialize();
	memcpy(bufReq.buffer, recvbuf, bufReq.length);
	req.deserialize(bufReq);

	/* make telegram */
	res->ver = (char)SystemConfigInfo::getVersion();
	res->rsv = 0x00;
	res->atyp = 0x01; /* only replay IPv4 */
	res->rmotInfo.dest_addr = (char*)malloc(4);
	memset(res->rmotInfo.dest_addr, 0x00, 4);
	res->rmotInfo.dest_port = 0;
	
	/* check vaild */
	if(false == SystemConfigInfo::IsPermitedSvr(req.cmd, req.dstInfo.dest_addr, req.dstInfo.dest_port))
	{
		/* connection not allowed by ruleset */
		repValue = 0x02;
	}
	
	else
	{
		switch(req.cmd)
		{
		case CMD_CONNECT:
			/* connect remote server */
			rmotfd = socket(AF_INET, SOCK_STREAM, 0);
			if(rmotfd < 0)
			{
				LOG_FATAL("(R) socket failure(@@@)!");
				varErrno = errno;
				repValue = this->getErrorCode("socket", varErrno);
			}
			
			else
			{
				Ipbuf = getAddressNum(req.atyp, req.dstInfo.dest_addr);
	
				rmotSvrInfo.sin_family = AF_INET;
				rmotSvrInfo.sin_port = htons(req.dstInfo.dest_port);
				rmotSvrInfo.sin_addr.s_addr = *(uint32_t*)Ipbuf;
	
				result = connect(rmotfd, (SA*)&rmotSvrInfo, rmotSvrLen);
				if(result < 0)
				{
					LOG_FATAL("(R) connect failure(@@@)!");
					varErrno = errno;
					repValue = this->getErrorCode("connect", varErrno);
				}
				connectionInfo.setIPAndPort(&rmotSvrInfo, connectionInfo.remote);

				/* Tcp connection route */
				currType = TCP_Type;
			}
			
			if(NULL != Ipbuf)
			{
				memcpy(res->rmotInfo.dest_addr, Ipbuf, 4);
			}
			res->rmotInfo.dest_port = req.dstInfo.dest_port;
			
			break;
			
		case CMD_BIND:
			/* BIND */
	
			break;
	
		case CMD_UDP_ASSOCIATE:
			/* UDP */
			rmotfd = socket(AF_INET, SOCK_DGRAM, 0);
			if(rmotfd < 0)
			{
				LOG_FATAL("(R) socket failure(@@@)!");
				varErrno = errno;
				repValue = this->getErrorCode("socket", varErrno);
			}
			char* ipv4num = (char*)inet_addr(selfIpv4.c_str());
			udpport = globeUdpPort ++;
			
			rmotSvrInfo.sin_family = AF_INET;
			rmotSvrInfo.sin_port = htons(udpport);
			rmotSvrInfo.sin_addr.s_addr = *(uint32_t*)ipv4num;
			
			result = bind(rmotfd, (SA*)&rmotSvrInfo, sizeof(struct sockaddr));
			if(result < 0)
			{
				LOG_FATAL("(R) bind failure(@@@)!");
				varErrno = errno;
				repValue = this->getErrorCode("bind", varErrno);
			}
			
			else
			{
				/* Udp connection route */
				currType = UDP_Type;
				memcpy(res->rmotInfo.dest_addr, ipv4num, 4);
				res->rmotInfo.dest_port = udpport;
			}
			break;
		}
	}

	res->rep = repValue;
	res->serialize(sendBuf);
	delete res;
	
	return 0;
}

/**********************************************
* member method
* getErrorCode 
**********************************************/
char SFW_TcpPort::getErrorCode(string funName, int varErrno)
{
	if(funName == "socket")
	{
		switch(varErrno)
		{
		case EACCES:
				
			break;

		case EAFNOSUPPORT:
			
			break;

		}
	}

	else if(funName == "connect")
	{
		switch(varErrno)
		{
		case EACCES:

			break;

		case EADDRINUSE:

			break;
		}
	}

	/* unassigned */
	return 0xff;
}

/**********************************************
* member method
* reset 
**********************************************/
void SFW_TcpPort::reset()
{
	::close(connfd);
	::close(rmotfd);
	connfd = -1;
	rmotfd = -1;
	tcpStatus = stClosed;
}

